package com.example.mysharding.config;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//


import com.example.mysharding.algo.ShardingStrategyFactory;
import com.example.mysharding.bean.ShardingDataSource;
import com.example.mysharding.bean.ShardingProperties;
import com.example.mysharding.mapperload.ShardingMapperBeanDefitionRegister;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.ShardingStrategyConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

@Configuration
@ConditionalOnClass({ShardingAutoConfigure.class})
@EnableConfigurationProperties({ShardingProperties.class})
@Import({ShardingMapperBeanDefitionRegister.class})
public class ShardingAutoConfigure {

    @Autowired
    private ShardingProperties shardingProperties;

    public ShardingAutoConfigure() {
    }

    @Bean(
        name = {"dataSource"}
    )
    @ConditionalOnMissingBean
    @ConditionalOnProperty(
        prefix = "shardingsphere.sharding",
        name = {"enabled"},
        havingValue = "true"
    )
    DataSource createDataSource() throws SQLException {
        // 构建数据源，这里使用的是Hikari
        Map<String, DataSource> dataSourceMap = this.buildDataSource(this.shardingProperties);
        // 构建配置，将sharding-jdbc-enchence-starter的简单配置转换为sharding-jdbc-spring-boot-starter的标准配置
        ShardingRuleConfiguration shardingRuleConfig = this.buildShardingRuleConfiguration(this.shardingProperties);
        shardingRuleConfig.setDefaultDataSourceName("ds0");
        Properties properties = this.buildOtherProperties(this.shardingProperties);
        DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, properties);
        return dataSource;
    }

    private Map<String, DataSource> buildDataSource(ShardingProperties shardingProperties) {
        Map<String, DataSource> dataSourceMap = new HashMap();
        Map<String, ShardingDataSource> shardingDataSourceMaps = shardingProperties.getShardingDataSources();
        for (String key : shardingDataSourceMaps.keySet()) {
            dataSourceMap.put(key, new HikariDataSource(this.buildHikariConfig(shardingDataSourceMaps.get(key))));
        }
        return dataSourceMap;
    }

    private Properties buildOtherProperties(ShardingProperties shardingProperties) {
        Properties properties = new Properties();
        if (StringUtils.equals(shardingProperties.getSqlShow(), "true")) {
            properties.setProperty("sql.show", Boolean.TRUE.toString());
        } else {
            properties.setProperty("sql.show", Boolean.FALSE.toString());
        }

        return properties;
    }

    private HikariConfig buildHikariConfig(ShardingDataSource shardingDataSource) {
        HikariConfig configuration = new HikariConfig();
        configuration.setDriverClassName(shardingDataSource.getDriverClassName());
        configuration.setJdbcUrl(shardingDataSource.getJdbcUrl());
        configuration.setUsername(shardingDataSource.getUsername());
        configuration.setPassword(shardingDataSource.getPassword());
        return configuration;
    }

    private ShardingRuleConfiguration buildShardingRuleConfiguration(ShardingProperties shardingProperties) {
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        Map<String, String> shardingTables = shardingProperties.getShardingTables();
        Iterator var4 = shardingTables.entrySet().iterator();

        while(var4.hasNext()) {
            Map.Entry<String, String> item = (Map.Entry)var4.next();
            TableRuleConfiguration tableRuleConfig = this.builTableRuleConfiguration(shardingProperties, item);
            shardingRuleConfig.getTableRuleConfigs().add(tableRuleConfig);
        }

        return shardingRuleConfig;
    }

    private TableRuleConfiguration builTableRuleConfiguration(ShardingProperties shardingProperties, Map.Entry<String, String> item) {
        String shardingTableName = (String)item.getKey();
        String shardingColumnName = (String)item.getValue();
        String actualDataNodesExpression = this.generaActualDataNodesExpression(shardingProperties, shardingTableName);
        TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration(shardingTableName, actualDataNodesExpression);
        tableRuleConfig.setTableShardingStrategyConfig(ShardingStrategyFactory.getShardingStrategy(shardingTableName, shardingColumnName, shardingProperties));
        String[] split = shardingColumnName.split(",");
        String bussinessId = split[0];
        int size = shardingProperties.getShardingDataSources().size();
        String dsStrategy = shardingProperties.getDsStrategy();
        if(size > 1 && io.micrometer.common.util.StringUtils.isNotEmpty(dsStrategy)) {
            try {
                Class<?> dsStrategyConfigClass = Class.forName(dsStrategy);
                if(ShardingStrategyConfiguration.class.isAssignableFrom(dsStrategyConfigClass)) {
                    ShardingStrategyConfiguration strategyConfiguration = (ShardingStrategyConfiguration)
                            dsStrategyConfigClass.getDeclaredConstructor().newInstance();
                    tableRuleConfig.setDatabaseShardingStrategyConfig(strategyConfiguration);
                } else {
                    tableRuleConfig.setDatabaseShardingStrategyConfig(ShardingStrategyFactory.getDefaultDsStrategy(bussinessId, size));
                }

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return tableRuleConfig;
    }

    /**
     * 简化按时间分表配置 & 后续维护
     */
    private String generaActualDataNodesExpression(ShardingProperties shardingProperties, String shardingTableName) {
        String tableMinIndex = shardingProperties.getTableMinIndex();
        String tableMaxIndex = shardingProperties.getTableMaxIndex();
        Integer minYear = Integer.parseInt(tableMinIndex.substring(0, 4));
        Integer maxYear = Integer.parseInt(tableMaxIndex.substring(0, 4));
        int dataSourceSize = shardingProperties.getShardingDataSources().keySet().size() - 1;
        int increment;
        if (minYear.equals(maxYear)) {
            increment = 0;
        } else {
            increment = maxYear - minYear;
        }

        String actualDataNodesExpression = "";
        StringBuilder sb = new StringBuilder(actualDataNodesExpression);
        if (increment == 0) {
            sb.append(String.format("ds$->{0.." + dataSourceSize + "}.%s_${%s..%s}", shardingTableName, tableMinIndex, tableMaxIndex));
        } else {
            for(int i = 0; i <= increment; ++i) {
                if (i == 0) {
                    sb.append(String.format("ds$->{0.." + dataSourceSize + "}.%s_${%s..%s}", shardingTableName, tableMinIndex, minYear + "12")).append(",");
                }

                if (i > 0 && i < increment) {
                    String tempYear = String.valueOf(minYear + i);
                    sb.append(String.format("ds$->{0.." + dataSourceSize + "}.%s_${%s..%s}", shardingTableName, tempYear + "01", tempYear + "12")).append(",");
                }

                if (i == increment) {
                    sb.append(String.format("ds$->{0.." + dataSourceSize + "}.%s_${%s..%s}", shardingTableName, maxYear + "01", tableMaxIndex));
                }
            }
        }

        return sb.toString();
    }
}
